package monitor

import (
	"fmt"
	"sort"
	"sync"
	"time"
)

const NORMAL_REQ = 500

type LatencyStats struct {
	// 增加Begin，End信息，便于计算压测速率信息
	// Begin： 压测开始时间
	// End：压测结束时间
	Begin, End time.Time

	// 统计成功次数，失败次数
	Succ, Fail int

	// 总压测次数
	Num int

	Mark time.Time

	// 总时延，最小时延，最大时延
	Dur, Min, Max time.Duration

	// 互斥锁，保证互操作时 goroutine 并发安全
	mu *sync.Mutex

	// 附加信息，便于最后统计汇总
	Ext string

	Percentile [NORMAL_REQ]int

	Longreq []int
}

func New(Ext string) *LatencyStats {
	s := &LatencyStats{
		Ext: Ext,
		mu:  &sync.Mutex{},
		Percentile: [NORMAL_REQ]int{},
		Longreq: []int{},
	}
	return s
}

func (s *LatencyStats) Start() {
	s.Mark = time.Now()
	if s.Begin.IsZero() {
		s.Begin = s.Mark
	}
}

func (s LatencyStats) String() string {
	return fmt.Sprintf("Begin: %v, End: %v, Succ: %v, Fail :%v, max-latency: %v, min-latency: %v",
		s.Begin.UnixNano(), s.End.UnixNano(), s.Succ, s.Fail, s.Max, s.Fail)
}

func (s *LatencyStats) record(n int) {

	if s.Mark.IsZero() {
		panic("not started")
	}

	now := time.Now()
	s.End = now
	dur := now.Sub(s.Mark)
	dur1 := dur / time.Duration(n)
	if dur1 < s.Min || s.Min == 0 {
		s.Min = dur1
	}
	if dur1 > s.Max {
		s.Max = dur1
	}
	s.Dur += dur
	s.Num += n
	ms := int(dur1 / time.Millisecond)
	if ms < NORMAL_REQ {
		s.Percentile[ms]++
	} else {
		s.Longreq = append(s.Longreq, ms)
	}
	s.Mark = time.Time{}
}

func (s *LatencyStats) RecordSucc(n int) {
	s.record(n)
	s.Succ += n
}

func (s *LatencyStats) RecordFail(n int) {
	s.record(n)
	s.Fail += n
}

func (s *LatencyStats) RatePerSec() int {
	durSec := s.Dur / time.Second
	if durSec > 0 {
		return s.Num / int(durSec)
	}
	return s.Num
}

func (s *LatencyStats) Avg() time.Duration {
	if s.Num > 0 {
		return s.Dur / time.Duration(s.Num)
	}
	return 0
}

func (s *LatencyStats) TPS() int {
	lat := s.End.Sub(s.Begin)
	return int(float64(s.Succ) / float64(lat) * float64(time.Second))
}

func (s *LatencyStats) Pct(p int) string {
	n := s.Num * p / 100
	i := 0
	t := 0
	for i < NORMAL_REQ && t < n {
		t += s.Percentile[i]
		i += 1
	}
	if t >= n {
		return fmt.Sprintf("< %dms", i + 1)
	} else {
		sort.Ints(s.Longreq)
		if len(s.Longreq) > n - t {
			return fmt.Sprintf("< %dms", s.Longreq[n - t] + 1)
		} else {
			return "NaN"
		}
	}
}

func (s *LatencyStats) Add(x *LatencyStats) {
	s.mu.Lock()
	defer func() {
		s.mu.Unlock()
	}()

	if s.Ext == "" {
		s.Ext = x.Ext
	}
	if s.Begin.IsZero()|| s.Begin.UnixNano() > x.Begin.UnixNano() {
		s.Begin = x.Begin
	}
	if s.End.IsZero() || s.End.UnixNano() < x.End.UnixNano() {
		s.End = x.End
	}

	if x.Min < s.Min || s.Min == 0 {
		s.Min = x.Min
	}
	if x.Max > s.Max {
		s.Max = x.Max
	}
	s.Dur += x.Dur
	s.Num += x.Num

	s.Succ += x.Succ
	s.Fail += x.Fail

	for i:=0; i<NORMAL_REQ; i++ {
		s.Percentile[i] += x.Percentile[i]
	}
	s.Longreq = append(s.Longreq, x.Longreq...)
}

// Merge merge multiple LatencyStats(chan *LatencyStats) generated by goroutines and return one LatencyStats
func Merge(s chan *LatencyStats) *LatencyStats {

	x := New("")
	x.Start()

	for i := range s {
		x.Add(i)
	}

	return x
}
